package com.whz.generic.enhance;

import com.whz.generic.uuid.UuidUtils;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewMethod;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.MemberValue;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 使用javassit动态生成一个类的复制类。
 *
 * @author whz
 */
@Slf4j
@UtilityClass
public class CommonClassEnhancer {

    private final ClassPool DEFAULT_CLASS_POOL = ClassPool.getDefault();

    private final EnhanceClassNameGenerator nameGer = new EnhanceClassNameGenerator.DefaultEnhanceClassNameGenerator();

    /**
     * 创建一个类的复制子类
     *
     * @return 返回一个复制后的子类Class
     */
    public static Class<?> newJavaSubClass(Class<?> parentClass, String subClassName) {
        // 子类名称
        if (StringUtils.isEmpty(subClassName)) {
            subClassName = getSubClassName(parentClass);
        }
        // 已经存在的直接返回
        Class<?> existClass = newJavaClassIfPresent(subClassName);
        if (Objects.nonNull(existClass)) {
            return existClass;
        }

        // 不存在的创建一个新的子类CtClass
        CtClass ctClass = newSubCtClass(parentClass, subClassName);

        // 编译后返回java的class
        return createJavaClass(subClassName, ctClass);
    }

    /**
     * 按照子类全名,创建子类的CtClass
     *
     * @param subClassName 子类名
     * @return 子类的CtClass
     */
    @SneakyThrows
    private static CtClass newSubCtClass(Class<?> parentClass, String subClassName) {
        return DEFAULT_CLASS_POOL.getAndRename(parentClass.getName(), subClassName);
    }

    private String getSubClassName(Class<?> superClass) {
        String name = superClass.getName();
        if (StringUtils.isEmpty(name)) {
            name = UuidUtils.generateUid();
        }
        return nameGer.generateName(name);
    }

    private Class<?> newJavaClassIfPresent(String classname) {
        CtClass subCtClass = loadCtClass(classname);
        if (subCtClass != null && subCtClass.isFrozen()) {
            log.info("在ClassPool的Cache中找到这个类，直接加载它！");
            return createJavaClass(classname, subCtClass);
        }
        return null;
    }

    /**
     * 按照类全名,获取CtClass
     *
     * @param className 类全名
     * @return CtClass
     */
    @SneakyThrows
    public static CtClass loadCtClass(final String className) {
        CtClass ctClass = DEFAULT_CLASS_POOL.getCtClass(className);
        if (!ctClass.isPrimitive() && !ctClass.isArray() && !ctClass.isInterface()
            && ctClass.isModified() && ctClass.isFrozen()) {
            ctClass.defrost();
        }
        return ctClass;
    }


    /**
     * 在新的class loader中加载class
     *
     * @param className 类名
     * @param ctClass   CtClass
     * @return Class
     */
    @SneakyThrows
    public static Class<?> createJavaClass(String className, CtClass ctClass) {
        MsCommonClassLoader loader =
                new MsCommonClassLoader(CommonClassEnhancer.class.getClassLoader(), ctClass.toBytecode());
        return loader.findClass(className);
    }


    @SneakyThrows
    private CtField loadCtField(CtClass fieldCtClass,
                                String fieldName,
                                CtClass classAddedBy,
                                int modifier) {
        CtField f = new CtField(fieldCtClass, fieldName, classAddedBy);
        f.setModifiers(modifier);
        return f;
    }

    /**
     * 向javassit的ctClass 添加字段
     *
     * @param target        目标类
     * @param fieldWrappers 添加的字段包装
     * @return 新的类
     */
    @SneakyThrows
    public Class<?> enhanceAllFieldToCtClass(final Class<?> target,
                                             final List<FieldWrapper> fieldWrappers) {
        Assert.notEmpty(fieldWrappers, "field wrappers must added");
        CtClass ctClass = loadCtClass(target.getName());

        if (ctClass.isFrozen()) {
            ctClass.defrost();
        }
        for (FieldWrapper newField : fieldWrappers) {
            addSingleField(ctClass, newField);
        }
        return createJavaClass(target.getName(), ctClass);
    }

    @SneakyThrows
    private void addSingleField(CtClass ctClass, FieldWrapper newField)
            throws CannotCompileException {
        final String fieldTypeName = newField.getType().getName();
        final String fieldName = newField.getName();
        // 常量池
        ConstPool constPool = ctClass.getClassFile().getConstPool();

        // field 上的注解
        final Annotation fieldAnnotation = new Annotation(
                newField.getAnnotation().getName(),
                constPool
        );

        // 初始化注解属性
        for (Map.Entry<String, MemberValue> attr : newField.getAnnotationAttributes().entrySet()) {
            fieldAnnotation.addMemberValue(attr.getKey(), attr.getValue());
        }

        // 属性附上注解
        AnnotationsAttribute fieldAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        fieldAttr.addAnnotation(fieldAnnotation);

        // 创建一个CtField
        CtField newCtField = loadCtField(
                loadCtClass(fieldTypeName),
                fieldName,
                ctClass,
                newField.getModifier()
        );
        newCtField.getFieldInfo().addAttribute(fieldAttr);

        // 最后给这个子类添加字段
        ctClass.addField(newCtField);

        // 为字段添加getter和setter
        ctClass.addMethod(CtNewMethod.getter(getter(fieldName), newCtField));
        ctClass.addMethod(CtNewMethod.setter(setter(fieldName), newCtField));
    }

    private String getter(String name) {
        return "get" + StringUtils.capitalize(name);
    }

    private String setter(String name) {
        return "set" + StringUtils.capitalize(name);
    }
}